﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace agree
{
	public abstract partial class ChartControl<T, W> : ChartResources<W>
		where T : ChartControl<T, W>
		where W : struct, IProximalContext<W>
	{
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		///
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

		public NotificationSource[] rgns;

		public void ClearSubscribers()
		{
			var tmp = rgns;
			this.rgns = null;
			//if (tmp != null && ChartDebugger != null)
			//	Task.Factory.StartNew(() =>
			//		{
			//			Thread.CurrentThread.Priority = ThreadPriority.Lowest;
			//			for (int i = 0; !f_disposed && i < tmp.Length; i++)
			//				tmp[i].Dispose();
			//		});
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// Subscription mechanism
		/// </summary>
		/// <remarks>
		/// Clearing subscribers allows references to active edges which were not productive during the operation to be released
		/// </remarks>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public partial struct NotificationSource : IEnumerable<ActiveEdge>, IDisposable
		{
			[DebuggerBrowsable(DebuggerBrowsableState.Never)]
			ActiveEdge m_last;

			[DebuggerBrowsable(DebuggerBrowsableState.Never)]
			public alib.Concurrency.cache_line cl1;

			[DebuggerBrowsable(DebuggerBrowsableState.Never)]
			ActiveEdge m_first;

			ChartControl<T, W> ctrl;

			bool f_check_combine;

			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			/// <summary>
			/// 
			/// </summary>
			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			public NotificationSource(ChartControl<T, W> ctrl, bool f_check_combine)
				: this()
			{
				this.ctrl = ctrl;
				this.f_check_combine = f_check_combine;
			}

			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			/// <summary>
			/// Subscribe an active edge to this notification list.
			/// </summary>
			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			[DebuggerHidden]
			void Add(ActiveEdge ae_new)
			{
				Debug.Assert(!ae_new.IsHoldOrRemove());

				int spins = 0;
				if (m_first != null || Interlocked.CompareExchange(ref m_first, ae_new, null) != null)
				{
					ActiveEdge ml = m_last ?? m_first;
					while (ml.m_next != null || Interlocked.CompareExchange(ref ml.m_next, ae_new, null) != null)
					{
						if (++spins > 30)
							Thread.Yield();
						ml = ml.m_next;
					}
				}
				m_last = ae_new;

				ae_new.SetSequenceId(ref ctrl.next_obj_id);
				ae_new.EndTransact(AtomicSeqState.Available);
			}

			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			/// <summary>
			/// This is the normal (non-retroactive) subscription delivery mechanism which sends new edges to existing
			/// active edges. Also opportunistically try to patch over defunct active edges that we might encounter. Do not 
			/// send to active edges with a higher sequence number than the new edge. Also try not to forward to subscribers 
			/// who are on packing hold although this can't be guaranteed because active objects are subject to asynchronous 
			/// entry of that state, causing them to possibly switch to inactive after we observe them. On the other 
			/// hand, regarding visible objects, the 'pending' state is only exited, never entered, so it will be successfully 
			/// banned but possibly missed, this latter condition being handled by the atomic sequence, which ensures
			/// that anything missed is sent retroactively.
			/// </summary>
			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			internal void SendToAllSubscribers(IChartObj<W> passive)
			{
				long i_seq = passive.Id;
				int coverage_count = passive.ProximalContext.CoverageCount;

				var e = f_check_combine ? FilteredEnumerator(passive.ProximalContext) : this.GetEnumerator();
				while (e.MoveNext())
				{
					ActiveEdge active = e.Current;

					if (ctrl is GenControl)
					{
						if (active.dtrs.Contains(passive))
							continue;
					}
					else
					{
						if (coverage_count > active.ArityLimit)
							continue;
					}

					if (!active.SeqIdBelow(i_seq))
						continue;

					using (ctrl.suspend_timing)
						if (!passive.WaitActive())
							return;

					active.TryMatchEdge(passive);
				}
			}

			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			/// <summary>
			/// Add the target to the list of subscribers. Because rule_targets is a list that is interlocked with the
			/// ChartEdge stamping sequence, Add() atomically imposes an (arbitrary) partition upon all past (and future)
			/// ChartEdges such that those which arrive prior to the sequencing point are sent by the retroactive mechanism,
			/// and ChartEdges sent afterwards will be posted to the target via the subscription delivery mechanism.
			/// </summary>
			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			public void Subscribe(ActiveEdge active)
			{
				/// add to a linked list and set a sequence barrier
				this.Add(active);
				long i_seq = active.Id;
				int arity_limit = active.ArityLimit;

				this.ctrl.bp("Add active edge", active);

				var e = ctrl.GetPassiveForActive(active);
				for (IChartObj<W> passive, prev = null; e.MoveNext(); prev = passive)
				{
					passive = (IChartObj<W>)e.Current;

					if (ctrl is GenControl)
					{
						if (active.dtrs.Contains(passive))
							continue;
					}
					else
					{
						if (passive.ProximalContext.CoverageCount > arity_limit)
							continue;
					}

					if (passive.SeqIdAbove(i_seq))
						continue;

					if (active.IsHoldOrRemove())
						return;

					if (passive.IsHoldOrRemove())
					{
						/// opportunistic removal of packed edges from the chart
						IChartObj tmp;
						if (prev != null && (tmp = passive.Next) != null)
							prev.TrySetNext(tmp, passive);
						continue;
					}

					using (ctrl.suspend_timing)
						if (!passive.WaitActive())
							continue;

					active.TryMatchEdge(passive);
				}
			}

			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			/// <summary>
			/// release ActiveEdge references in the linked-list so the objects can be collected individually. Although 
			/// clearing the list head (or releasing the array of notification sources) might be thought sufficient, we 
			/// don't want to keep a whole chain alive if someone has sequestered a solitary reference to some single 
			/// active edge in the middle of the list. (But who would be doing this? The chart debugger, probably)
			/// </summary>
			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			public void Dispose()
			{
				ActiveEdge ae = m_first;
				ctrl = null;
				m_first = m_last = null;
				while (ae != null)
				{
					var _tmp = ae.m_next;
					ae.m_next = null;
					ae = _tmp;
				}
			}

			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			/// <summary>
			/// calls 'CanCombineWith' to filter the active edges returned
			/// </summary>
			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			public IEnumerator<ActiveEdge> FilteredEnumerator(W ctx)
			{
				ActiveEdge tmp;
				for (ActiveEdge active = m_first, prev = null; active != null; prev = active, active = (ActiveEdge)active.m_next)
				{
					if (active.IsHoldOrRemove())
					{
						/// opportunistic removal of expired active edges; it doesn't matter if it doesn't transact.
						if (prev != null && (tmp = active.m_next) != null)
							Interlocked.CompareExchange(ref prev.m_next, tmp, active);
						continue;
					}
					if (ctx.CanCombineWith(active.ctx))
						yield return active;
				}
			}

			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			/// <summary>
			/// raw access; no operational analysis except for opportunistic removal
			/// </summary>
			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }
			public IEnumerator<ActiveEdge> GetEnumerator()
			{
				ActiveEdge tmp;
				for (ActiveEdge active = m_first, prev = null; active != null; prev = active, active = (ActiveEdge)active.m_next)
				{
					if (active.IsHoldOrRemove())
					{
						/// opportunistic removal of expired active edges; it doesn't matter if it doesn't transact.
						if (prev != null && (tmp = active.m_next) != null)
							Interlocked.CompareExchange(ref prev.m_next, tmp, active);
						continue;
					}
					yield return active;
				}
			}
		};
	};
}
